﻿using System;
using System.Diagnostics;
using System.Linq;
using BLL.Configuration;
using BLL.Models.Users;
using BLL.Services.Administrators;
using BLL.Services.Users;

namespace BLL.Security
{
    internal class SecurityService :  ISecurityService
    {
        private readonly UsersCollection _usersCollection;

        private readonly AdministratorsCollection _administratorsCollection;

        private readonly IGlobalSettings _settings;

        public SecurityService(UsersCollection usersCollection, AdministratorsCollection administratorsCollection, IGlobalSettings globalSettings)
        {
            _administratorsCollection = administratorsCollection;
            _usersCollection = usersCollection;
            _settings = globalSettings;
        }

        /// <summary>
        /// Authenticates user in the system.
        /// </summary>
        /// <param name="userName">User name.</param>
        /// <param name="password">User password.</param>
        /// <returns>
        /// Authentication result for provided credentials.
        /// </returns>
        public AuthenticationStatus AuthenticateUser(string userName, string password)
        {
            var user = _usersCollection.GetByEmailOrLogin(userName);

            if (null == user)
                return AuthenticationStatus.NotFound;

            if (!string.IsNullOrEmpty(password))
            {
                // We use registration date as a salt in order to avoid extra field and increase secutiry
                byte[] salt = user.Salt;
                byte[] encryptedPassword = Cryptographer.GenerateHash(password, salt);

                if (!encryptedPassword.SequenceEqual(user.PasswordHash))
                {
                    Trace.TraceWarning("Authentication failure: invalid password for user {0}",
                                       userName);
                    return Models.Users.AuthenticationStatus.PaswordInvalid;
                }
            }

            if (UserStatus.NotVerified == user.Status)
            {
                Trace.TraceWarning("Authentication failure: account {0} is not verified", userName);
                return AuthenticationStatus.NotVerified;
            }

            if (UserStatus.Inactive == user.Status)
            {
                Trace.TraceWarning("Authentication failure: account {0} is disabled", userName);
                return AuthenticationStatus.Disabled;
            }

            Trace.TraceInformation("User {0} has been successfully authenticated", userName);

            return AuthenticationStatus.Success;
        }

        /// <summary>
        /// Authenticates administrator in the system.
        /// </summary>
        /// <param name="userName">Administrator user name.</param>
        /// <param name="password">Administrator password.</param>
        /// <returns>
        /// Authentication result for provided credentials.
        /// </returns>
        public Models.Administrators.AuthenticationStatus AuthenticateAdministrator(string userName, string password)
        {
            var admin = _administratorsCollection.GetByLogin(userName);
            if (null == admin)
                return Models.Administrators.AuthenticationStatus.NotFound;

            if (!string.IsNullOrEmpty(password))
            {
                // We use registration date as a salt in order to avoid extra field and increase secutiry
                byte[] salt = admin.Salt;
                byte[] encryptedPassword = Cryptographer.GenerateHash(password, salt);

                if (!encryptedPassword.SequenceEqual(admin.Password))
                {
                    Trace.TraceWarning("Authentication failure: invalid password for administrator {0}",
                                       userName);
                    return Models.Administrators.AuthenticationStatus.PaswordInvalid;
                }
            }

            Trace.TraceInformation("Administrator {0} has been successfully authenticated", userName);

            return Models.Administrators.AuthenticationStatus.Success;
        }

        /// <summary>
        /// Verifies system account credentials.
        /// </summary>
        /// <param name="userName">System user name.</param>
        /// <param name="password">Sytem user password.</param>
        /// <returns>
        /// true if credentials are correct; false otherwise.
        /// </returns>
        public bool AuthenticateSystemAccount(string userName, string password)
        {
            byte[] encryptedPassword = Cryptographer.GenerateHash(password);
            byte[] hashToCompare = Convert.FromBase64String(_settings.Security.SysAdminPwdHash);

            return encryptedPassword.SequenceEqual(hashToCompare) &&
                   userName == UserTypes.SysAdmin.ToString();
        }

        /// <summary>
        /// Authorizes the specified user in the system.
        /// </summary>
        /// <param name="userName">User name of the account to authorize.</param>
        /// <param name="accountType">User type to authrize account against.</param>
        /// <param name="assignedRoles">
        /// Receives roles assigned to the specified account. Will be left unchanged for system and
        /// administrative accounts.
        /// </param>
        /// <returns>
        /// True if account is successfully authorized, false otherwise.
        /// </returns>
        public bool AuthorizeAccount(string userName, UserTypes accountType, ref UserRoles assignedRoles)
        {
            if (UserTypes.EndUser == accountType)
            {
                var user = _usersCollection.GetByEmailOrLogin(userName);

                // All inactive users will fail to authorize
                if (null != user)
                    assignedRoles = (user.Status == UserStatus.Active) ? UserRoles.EndUser : UserRoles.None;
                else
                    assignedRoles = UserRoles.None;

                if (UserRoles.None == assignedRoles)
                    Trace.TraceInformation("Failed to authorize user {0}", userName);

                return UserRoles.None != assignedRoles;
            }

            if (UserTypes.Administrator == accountType)
                return _administratorsCollection.GetByLogin(userName)!=null;

            return userName == UserTypes.SysAdmin.ToString();
        }
    }
}